package com.framework.config;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import com.framework.bdt.enumClass.DataSourceType;
import com.framework.config.datasource.CustomRoutingDataSource;
import com.framework.interceptor.MybatisLogInterceptor;
import com.framework.stereotype.MyMapper;

@Configuration
@MapperScan(
		basePackages="com"
		, annotationClass=MyMapper.class
)
@EnableTransactionManagement(order=2, proxyTargetClass=true)
public class MybatisConfig {

	private static final Logger logger = LoggerFactory.getLogger(MybatisConfig.class);

	@Value("${master.driverClass}")
	private String masterDriverClassName;

	@Value("${master.url}")
	private String masterUrl;

	@Value("${master.username}")
	private String masterUsername;

	@Value("${master.password}")
	private String masterPassword;

	@Value("${slave.url}")
	private String slaveUrl;

	@Value("${basePackages}")
	private String BASE_PACKAGES;

	private static final String MYBATIS_CONFIG_FILE_PATH = "mybatis-config.xml";
	private static final String MYBATIS_MAPPER_LOCATIONS = "classpath*:mapper/*-mapper.xml";

	@Bean
	public CustomRoutingDataSource dataSource(){
		CustomRoutingDataSource dataSource = new CustomRoutingDataSource();
		Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
		targetDataSources.put(DataSourceType.MASTER, masterDataSource());
		targetDataSources.put(DataSourceType.SLAVE, slaveDataSource());
		dataSource.setTargetDataSources(targetDataSources);
		dataSource.setDefaultTargetDataSource(masterDataSource());
		return dataSource;
	}

	@Bean(destroyMethod = "close")
	public DataSource masterDataSource() {
		BasicDataSource dataSource = new BasicDataSource();
		dataSource.setDriverClassName(this.masterDriverClassName);
		dataSource.setUrl(this.masterUrl);
		dataSource.setUsername(this.masterUsername);
		dataSource.setPassword(this.masterPassword);
		return dataSource;
	}

	@Bean(destroyMethod = "close")
	public DataSource slaveDataSource() {
		BasicDataSource dataSource = new BasicDataSource();
		dataSource.setDriverClassName(this.masterDriverClassName);
		dataSource.setUrl(this.slaveUrl);
		dataSource.setUsername(this.masterUsername);
		dataSource.setPassword(this.masterPassword);
		return dataSource;
	}

	@Bean
	public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		sqlSessionFactoryBean.setDataSource(this.dataSource());
		sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(MYBATIS_CONFIG_FILE_PATH));
		sqlSessionFactoryBean.setTypeAliasesPackage(BASE_PACKAGES);
		sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MYBATIS_MAPPER_LOCATIONS));
		sqlSessionFactoryBean.setPlugins(new Interceptor[]{ new MybatisLogInterceptor()});
		return sqlSessionFactoryBean;
	}

	@Bean
	public SqlSessionTemplate sqlSessionTemplate() throws Exception {
		return new SqlSessionTemplate(this.sqlSessionFactory().getObject());
	}

	@Bean
	public PlatformTransactionManager transactionManager() throws Exception {
		return new DataSourceTransactionManager(this.dataSource());
	}

	@Bean
	public TransactionInterceptor transactionAdvice() throws Exception {
		TransactionInterceptor transactionAdvice = new TransactionInterceptor();
		transactionAdvice.setTransactionManager(transactionManager());

		NameMatchTransactionAttributeSource readOnlyMethodMapTransactionAttributeSource = new NameMatchTransactionAttributeSource();
		DefaultTransactionAttribute readOnlyDefaultTransactionAttribute = new DefaultTransactionAttribute();
		readOnlyDefaultTransactionAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
		readOnlyDefaultTransactionAttribute.setReadOnly(true);
		readOnlyMethodMapTransactionAttributeSource.addTransactionalMethod("get*", readOnlyDefaultTransactionAttribute);
		logger.info("readOnlyMethodMapTransactionAttributeSource to Map : {}", readOnlyMethodMapTransactionAttributeSource.toString());

		NameMatchTransactionAttributeSource methodMapTransactionAttributeSource = new NameMatchTransactionAttributeSource();
		methodMapTransactionAttributeSource.addTransactionalMethod("*", new DefaultTransactionAttribute());
		logger.info("methodMapTransactionAttributeSource to Map : {}", methodMapTransactionAttributeSource.toString());

		NameMatchTransactionAttributeSource[] transactionAttributeSource
			= new NameMatchTransactionAttributeSource[]{
				readOnlyMethodMapTransactionAttributeSource
				, methodMapTransactionAttributeSource
			};
		transactionAdvice.setTransactionAttributeSources(transactionAttributeSource);
		return transactionAdvice;
	}
}
